package com.chang.ware.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chang.common.exception.NoStockException;
import com.chang.common.to.mq.StockDetailTo;
import com.chang.common.to.mq.StockLockedTo;
import com.chang.common.utils.PageUtils;
import com.chang.common.utils.Query;
import com.chang.common.utils.R;
import com.chang.common.vo.SkuStockVo;
import com.chang.ware.entity.WareOrderTaskDetailEntity;
import com.chang.ware.entity.WareOrderTaskEntity;
import com.chang.ware.entity.WareSkuEntity;
import com.chang.ware.feign.OrderFeignClient;
import com.chang.ware.feign.ProductFeignService;
import com.chang.ware.mapper.WareSkuDao;
import com.chang.ware.service.WareSkuService;
import com.chang.ware.vo.OrderItemVo;
import com.chang.ware.vo.WareSkuLockVo;
import com.rabbitmq.client.Channel;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;

@Slf4j
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    ProductFeignService productFeignService;

    @Resource
    WareOrderTaskDetailServiceImpl wareOrderTaskDetailService;

    @Resource
    WareOrderTaskServiceImpl wareOrderTaskService;

    @Resource
    RabbitTemplate rabbitTemplate;

    @Resource
    OrderFeignClient orderFeignClient;

    @Resource
    private WareSkuDao wareSkuDao;

    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        LambdaQueryWrapper<WareSkuEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        String skuId = (String) params.get("skuId");
        if (StringUtils.hasLength(skuId)) {
            lambdaQueryWrapper.eq(WareSkuEntity::getSkuId, skuId)
                    .or()
                    .like(WareSkuEntity::getSkuName, skuId);
        }
        String wareId = (String) params.get("wareId");
        if (StringUtils.hasLength(wareId)) {
            lambdaQueryWrapper.eq(WareSkuEntity::getWareId, wareId);
        }
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                lambdaQueryWrapper
        );
        return new PageUtils(page);
    }

    @Transactional
    @Override
    public void addStock(Long purchaseDetailId, Long wareId, Integer skuNum) {
        // baseMapper.addStock(purchaseId,wareId,skuNum);
        LambdaQueryWrapper<WareSkuEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(WareSkuEntity::getWareId, wareId)
                .eq(WareSkuEntity::getSkuId, purchaseDetailId);// purchaseId采购项id就是sku商品id
        WareSkuEntity wareSkuEntity = new WareSkuEntity();
        wareSkuEntity.setStock(skuNum);
        List<WareSkuEntity> wareSkuList = list(wrapper);
        if (wareSkuList.isEmpty()) {
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setSkuId(purchaseDetailId);
            wareSkuEntity.setStockLocked(0);
            // 这里只是一个冗余字段的获取 如果失败，整个事务无需回滚  设置方法:
            // 方法1: 主动catch异常
            // 方法2: TODO
            try {
                R info = productFeignService.getInfo(purchaseDetailId);
                if (info.getCode() == 0) {
                    Map<String, Object> map = (Map<String, Object>) info.get("skuInfo");
                    String skuName = (String) map.get("skuName");
                    wareSkuEntity.setSkuName(skuName);
                }
            } catch (Exception e) {
            }
            // 调用远程，查询商品
            wareSkuEntity.setWareId(wareId);
            save(wareSkuEntity);
        } else {
            List<WareSkuEntity> wareSkuEntities = wareSkuList.stream().peek(item -> item.setStock(item.getStock() + skuNum)).toList();
            updateBatchById(wareSkuEntities);
        }
    }


    /*
     * @Author Chang
     * @Date 2024/6/3 9:56
     * @Description 单件商品可能在不同仓库有不同库存，并且还需要减去客户下单后锁定的库存。此处查询库存时需要使用sql语句求和
     * @Param [skuIds]
     * @Return java.util.List<com.chang.ware.vo.SkuStockVo>
     * @Since version 1.0
     */
    @Override
    public List<SkuStockVo> getStockBySkuId(List<Long> skuIds) {
        SkuStockVo skuStockVo = new SkuStockVo();

        return skuIds.stream().map((skuId) -> {
            Integer countStock = baseMapper.CountStock(skuId);
            skuStockVo.setSkuId(skuId);
            // 如果countStock有值就赋值，为空就赋值为0
            skuStockVo.setStock(Objects.requireNonNullElse(countStock, 0));
            return skuStockVo;
        }).toList();
    }

    @Override
    public Map<Long, Integer> getBatchStockBySkuIds(List<Long> skuIds) {
        Map<Long, Integer> map = new HashMap<>();
        // 这里有点纳闷,根据集合批量查询出来就是空的,但是,将集合拆开挨个查询就有数据
        // List<WareSkuEntity> wareSkuEntities = baseMapper.selectList(new LambdaQueryWrapper<WareSkuEntity>().eq(WareSkuEntity::getSkuId, skuIds));
        // System.out.println("wareSkuEntities = " + wareSkuEntities); // null
        skuIds.forEach(skuId -> {
            WareSkuEntity wareSkuEntity = getOne(new LambdaQueryWrapper<WareSkuEntity>().eq(WareSkuEntity::getSkuId, skuId));
            if (wareSkuEntity != null) {
                map.put(wareSkuEntity.getSkuId(), wareSkuEntity.getStock());
            }
        });
        log.info("商品库存批量查询结果:map->{}", map);
        return map;
    }

    // 老师的逻辑是一个仓库不够就额外找一个仓库 但是弹幕说的对 不可能多个仓库发货的，成本太高。只会遍历仓库，找到库存够的，就在这个仓库发货。我这边按照弹幕的逻辑来
    // 为订单锁定库存

    /**
     * 库存解锁的场景
     * 1,订单创建成功,但是订单超时未支付,被系统自动取消/被用户手动取消,都要解锁库存
     * 2,订单创建成功,但是接下来的业务调用失败,导致订单回滚,之前锁定的库存就要回滚
     * <p>
     * 这里的分布式事务解决方案是使用TCC事务补偿机制，创建订单的时候将订单的详情保存到一个工作单，如果报错需要回滚库存，这边就根据订单的记录来回滚库存数量
     */
    @Transactional(rollbackFor = NoStockException.class)
    @Override
    public Boolean orderLockStock(WareSkuLockVo wareSkuLockVo) {

        // 保存库存工作单的详情 也就是中间操作留痕 用于追溯回滚
        WareOrderTaskEntity wareOrderTask = new WareOrderTaskEntity();
        wareOrderTask.setOrderSn(wareSkuLockVo.getOrderSn());
        wareOrderTaskService.save(wareOrderTask);

        List<OrderItemVo> locks = wareSkuLockVo.getLocks();
        List<Long> skuList = new ArrayList<>();
        locks.forEach(lock -> {
            skuList.add(lock.getSkuId());
        });
        // 取出所有选中的商品，并需要商品大于0
        List<WareSkuEntity> wareSkuEntities = this.baseMapper.selectList(new LambdaQueryWrapper<WareSkuEntity>()
                .in(WareSkuEntity::getSkuId, skuList).and(item -> item.gt(WareSkuEntity::getStock, 0)));

        // 这里老师还想把订单所有商品的锁定情况全部封装起来,进行返回,但是结果只需要返回是否锁定成功,以及锁定失败时是哪个商品没有库存就好了
        // LockStockResultVo lockStockResultVo = new LockStockResultVo();
        // 购物车中每个商品对应的仓库的数量
        log.info("取出仓库所有选中的商品wareSkuEntities=>{}", wareSkuEntities);

        List<WareOrderTaskDetailEntity> wareOrderTaskDetailList = new ArrayList<>();
        locks.forEach(item -> {

            Long skuId = item.getSkuId();
            Integer skuCount = item.getCount(); // 获取购物车中商品数量
            List<WareSkuEntity> wareSkulist = wareSkuEntities.stream().filter(wareSkuItem -> wareSkuItem.getSkuId().equals(skuId)).toList();
            log.info("获取当前商品所有仓库库存数据wareSkuList=>{}", wareSkulist);
            boolean isLocked = false;

            for (WareSkuEntity wareSkuEntity : wareSkulist) {
                // 如果 现有库存 减去已被其他订单锁定的库存后 大于等于当前订单需要锁定的库存 再接着往下执行
                if (wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() >= skuCount && skuCount != 0) {
                    Long wareId = wareSkuEntity.getWareId();
                    Long result = wareSkuDao.lockSkuStock(skuId, wareId, skuCount);
                    log.info("库存锁定返回结果=>{}", result);
                    // 如果锁定成功 则将当前商品锁定了几件的工作单记录发送给mq
                    // 如果锁定失败 前面保存的工作单就回滚
                    if (result == 1) {
                        isLocked = true;
                        // 库存锁定详情
                        WareOrderTaskDetailEntity orderTaskDetail = new WareOrderTaskDetailEntity(null, skuId, wareSkuEntity.getSkuName(), skuCount, wareOrderTask.getId(), wareId, 1);
                        wareOrderTaskDetailList.add(orderTaskDetail);
                    }
                    break; // 如果某个仓库库存充足，那其他仓库就不用再看了
                }
            }
            if (!isLocked) {
                // 当前商品所有仓库商品都没锁定成功,则直接抛异常,不再进行剩余商品的库存锁定
                throw new NoStockException(skuId);
            }
        });

        // 如果库存全部锁定成功 这里统一保存工作单详情 并发送MQ   视频里老师的逻辑是一个商品库存锁定成功就往mq发送一次消息 我这里直接统一发送了
        wareOrderTaskDetailService.saveBatch(wareOrderTaskDetailList);

        // 封装工作单TO对象，用于向MQ发送消息
        StockLockedTo stockLockedTo = new StockLockedTo();

        List<StockDetailTo> stockDetailToList = new ArrayList<>();

        wareOrderTaskDetailList.forEach(wareOrderTaskDetailEntity -> {
            StockDetailTo stockDetailTo = new StockDetailTo();
            BeanUtils.copyProperties(wareOrderTaskDetailEntity, stockDetailTo);
            stockDetailToList.add(stockDetailTo);
        });

        stockLockedTo.setId(wareOrderTask.getId());
        stockLockedTo.setDetailTo(stockDetailToList);
        // 将订单信息发送到死信队列 如果超时用于解锁已被锁定的库存
        rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", stockLockedTo);

        return true;
    }

    @Override
    public void unlockStock(StockLockedTo stockLockedTo, Message message, Channel channel) throws IOException {
        System.out.println("收到解锁库存的消息，准备解锁库存。。。。。。");
        Long orderTaskId = stockLockedTo.getId(); // 这里可以根据工作单id获取订单sn，然后查询订单状态
        // 解锁 如果没有订单，但是库存却还在锁定 那就必须解锁
        // 如果有订单  订单状态是已经取消，则需要解锁订单 如果没有取消则不能解锁
        WareOrderTaskEntity orderTask = wareOrderTaskService.getById(orderTaskId);
        // 只有没被解锁的详细库存工作单才能被拿出来处理
        List<WareOrderTaskDetailEntity> taskDetailEntities = wareOrderTaskDetailService.list(new LambdaQueryWrapper<WareOrderTaskDetailEntity>()
                .eq(WareOrderTaskDetailEntity::getTaskId, orderTaskId).eq(WareOrderTaskDetailEntity::getLockStatus, 1));

        String orderSn = orderTask.getOrderSn();
        try {
            R result = orderFeignClient.getOrderStatusByOrderSn(orderSn);

            Integer status = JSONUtil.toBean(result.getData(), Integer.class);
            if (status == null) {
                unlockStock(taskDetailEntities);
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                System.out.println("远程调用查询订单结果为空,解锁库存.....");
            }
            // 订单不存在或者订单已经取消状态都需要解锁库存
            if (status == 4) {
                unlockStock(taskDetailEntities);
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("订单已取消,库存解锁成功....");
            }
        } catch (Exception e) {
            throw new RuntimeException("远程查询订单状态失败....");
        }
    }

    // 用订单系统过来的订单sn查询工作单进而解锁库存
    @Override
    public void unlockStock(String orderSn, Message message, Channel channel) {
        // 解锁 如果没有订单，但是库存却还在锁定 那就必须解锁
        // 如果有订单  订单状态是已经取消，则需要解锁订单 如果没有取消则不能解锁
        WareOrderTaskEntity orderTask = wareOrderTaskService.getOne(new LambdaQueryWrapper<WareOrderTaskEntity>().eq(WareOrderTaskEntity::getOrderSn, orderSn));

        List<WareOrderTaskDetailEntity> taskDetailEntities = wareOrderTaskDetailService.list(new LambdaQueryWrapper<WareOrderTaskDetailEntity>()
                .eq(WareOrderTaskDetailEntity::getTaskId, orderTask.getId()).eq(WareOrderTaskDetailEntity::getLockStatus, 1));

        try {
            // TODO 这里应该加锁
            // 再确认一遍订单状态
            R result = orderFeignClient.getOrderStatusByOrderSn(orderSn);

            Integer status = JSONUtil.toBean(result.getData(), Integer.class);
            if (status == null) {
                unlockStock(taskDetailEntities);
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                System.out.println("远程调用查询订单结果为空,解锁库存.....");
            }
            // 订单不存在或者订单已经取消状态都需要解锁库存
            if (status == 4) {
                unlockStock(taskDetailEntities);
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                log.info("订单已取消,库存解锁成功....");
            }
        } catch (Exception e) {
            throw new RuntimeException("远程查询订单状态失败....");
        }
    }

    private void unlockStock(List<WareOrderTaskDetailEntity> taskDetailEntities) {
        taskDetailEntities.forEach(taskDetailEntity -> {
            // 这里的库存工作详情单还必须是已经锁定状态才能解锁库存
            if (taskDetailEntity.getLockStatus() == 1) {
                wareSkuDao.unlockStock(taskDetailEntity.getSkuId(), taskDetailEntity.getWareId(), taskDetailEntity.getSkuNum());
                WareOrderTaskDetailEntity taskDetail = new WareOrderTaskDetailEntity();
                // 库存解锁之后库存工作单也需要改为解锁状态
                taskDetail.setId(taskDetailEntity.getId());
                taskDetail.setLockStatus(2); // 锁定状态变为已解锁
                wareOrderTaskDetailService.updateById(taskDetail);
            }
        });
    }
}